const std = @import("std");
const os = @import("../os.zig");
const sys = os.syscall;
const path_t = sys.SysCall.path_t;
const pid_t = sys.SysCall.pid_t;
const clock = os.trap.clock;
const Proc = os.proc.Proc;

pub fn shutdown(_: *Proc, code: u16) !noreturn {
    @branchHint(.cold);
    os.proc.shutdown(code);
}

pub fn fork(p: *Proc) !pid_t {
    return try p.fork();
}

pub fn getpid(p: *Proc) !usize {
    return p.pid;
}

pub fn exit(p: *Proc, status: i32) !noreturn {
    p.exit(status);
}

pub fn wait(p: *Proc, status: ?*i32) !usize {
    return try p.wait(status);
}

pub fn kill(_: *Proc, pid: pid_t) !void {
    return os.proc.kill(pid);
}

pub fn sbrk(p: *Proc, increment: isize) !usize {
    const addr = p.ctx.uproc.sz;
    if (increment < 0) {
        try p.shrink(@intCast(-increment));
    } else {
        // Lazily allocate memory for this process: increase its memory size but don't allocate memory.
        // If the processes uses the memory, vmfault() will allocate it.
        p.ctx.uproc.sz += @intCast(increment);
    }
    return addr;
}

pub fn pause(p: *Proc, nticks: usize) !void {
    clock.lock.acquire();
    defer clock.lock.release();
    const ticks = clock.ticks;

    while (clock.ticks - ticks < nticks) {
        if (p.isKilled()) return error.ProcessNotFound;
        // will be woken by timer interrupts
        p.sleep(&clock.ticks, &clock.lock);
    }
}

pub fn uptime(_: *Proc) !usize {
    clock.lock.acquire();
    defer clock.lock.release();

    return clock.ticks;
}

pub fn trace(p: *Proc, mask: usize) !void {
    p.ctx.uproc.mask = mask;
}

///  Loads a new program into the current process's address space, replacing the previous program.
///
///  Argument Handling:
///  - Copies argv strings from OLD user stack to kernel buffers
///  - Destroys OLD address space (pages, pagetable, stack)
///  - Stack contents rebuilt from scratch (no direct old-to-new copy)
///  - Maintains RISC-V 16-byte stack alignment
pub fn exec(p: *Proc, path: path_t, argv: [*:null]const ?[*:0]const u8) !usize {
    var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator);
    defer arena.deinit();
    const allocator = arena.allocator();

    var buffer: [32]u8 = undefined;
    var reader = p.reader(.auto, &buffer, @ptrCast(argv));

    var len: usize = 0;
    var list = try allocator.alloc([:0]const u8, os.MAXARG);

    for (0..os.MAXARG) |i| {
        const addr = try reader.io_r.takeInt(usize, os.native_endian);
        if (addr == 0) break;
        const buf = try allocator.create([os.fs.MAXPATH]u8);
        list[i] = try p.ctx.uproc.fetchStr(addr, buf);
        len += 1;
    }

    if (len == 0) return error.InvalidArgument;
    return p.exec(path, list[0..len]);
}
